"""
Interactive Brokers adapter (Client‑Portal REST)
===============================================

Only the handful of endpoints required by *Alpha‑Factory* are wrapped.
The adapter is **stateless** – no background polling thread – so it works
inside any async task‑loop (e.g. FastAPI, Celery, Rocketry).

Env‑vars
--------
ALPHA_IBKR_HOST       broker‑gateway host  (default ``127.0.0.1``)
ALPHA_IBKR_PORT       broker‑gateway port  (default ``5000``)
ALPHA_IBKR_SSO_TOKEN  SSO auth token generated by ``/sso/flex/token``
"""

from __future__ import annotations

import os
from typing import Any, Final

import httpx
from tenacity import AsyncRetrying, retry_if_exception_type, stop_after_attempt, wait_exponential

from .logger import get_logger          # tiny helper already in repo
from .types import TradeBrokerProtocol  # = the `Protocol` shown above

_LOG = get_logger(__name__)

_HOST: Final[str] = os.getenv("ALPHA_IBKR_HOST", "127.0.0.1")
_PORT: Final[str] = os.getenv("ALPHA_IBKR_PORT", "5000")
_BASE: Final[str] = f"http://{_HOST}:{_PORT}/v1/api"
_TOKEN: Final[str | None] = os.getenv("ALPHA_IBKR_SSO_TOKEN")

_HEADERS = {"Content-Type": "application/json"}
if _TOKEN:
    _HEADERS["Authorization"] = f"Bearer {_TOKEN}"


class InteractiveBrokersBroker(TradeBrokerProtocol):  # noqa: D101
    #: 2 → 32 s back‑off, 3 attempts in total
    _retry = AsyncRetrying(
        wait=wait_exponential(multiplier=2, min=2, max=32),
        stop=stop_after_attempt(3),
        retry=retry_if_exception_type(httpx.HTTPError),
        reraise=True,
    )

    def __init__(self) -> None:
        if _TOKEN is None:
            raise RuntimeError("ALPHA_IBKR_SSO_TOKEN is not set")

        self._http = httpx.AsyncClient(base_url=_BASE, headers=_HEADERS, timeout=10.0)

    # ------------------------------------------------------------------ #
    # TradeBroker interface                                              #
    # ------------------------------------------------------------------ #
    async def submit_order(
        self,
        symbol: str,
        qty: float,
        side: str,
        type: str = "MKT",
    ) -> str:
        """Market/LIMIT order – returns IB order id (str)."""
        order: dict[str, Any] = {
            "conid": await self._conid(symbol),
            "secType": "STK",
            "orderType": type,
            "price": 0,
            "side": side.lower(),
            "quantity": int(qty),
            "tif": "DAY",
        }
        async for attempt in self._retry:
            with attempt:
                r = await self._http.post("/iserver/account/{acc}/orders".format(acc=await self._account()), json=order)
        oid = r.json()["id"]
        _LOG.info("IBKR order %s accepted for %s %s@%s", oid, side, qty, symbol)
        return str(oid)

    async def get_position(self, symbol: str) -> float:  # noqa: D401
        """Return **signed** position in *shares* (+long / −short)."""
        conid = await self._conid(symbol)
        async for attempt in self._retry:
            with attempt:
                r = await self._http.get(f"/portfolio/{await self._account()}/position/{conid}")
        pos = next((p for p in r.json() if p["conid"] == conid), None)
        return float(pos["position"]) if pos else 0.0

    async def get_cash(self) -> float:  # noqa: D401
        async for attempt in self._retry:
            with attempt:
                r = await self._http.get(f"/portfolio/{await self._account()}/summary")
        cash = float(next(item for item in r.json() if item["tag"] == "CashBalance")["value"])
        return cash

    # ------------------------------------------------------------------ #
    # Helper calls (cached)                                              #
    # ------------------------------------------------------------------ #
    _acc: str | None = None
    async def _account(self) -> str:
        if self._acc:
            return self._acc
        r = await self._http.get("/portfolio/accounts")
        self._acc = r.json()["accounts"][0]
        return self._acc

    _conids: dict[str, str] = {}
    async def _conid(self, symbol: str) -> str:
        if symbol in self._conids:
            return self._conids[symbol]
        r = await self._http.get("/iserver/secdef/search", params={"symbol": symbol, "assetClass": "STK"})
        conid = r.json()[0]["conid"]
        self._conids[symbol] = conid
        return conid

    # ------------------------------------------------------------------ #
    async def __aenter__(self):  # context‑manager sugar
        return self

    async def __aexit__(self, *_exc):
        await self._http.aclose()
